#include "CL_RSS_Quelle.h"
#include<Poco/Net/HTTPClientSession.h>
#include<Poco/Net/HTTPRequest.h>
#include<Poco/Net/HTTPResponse.h>
#include<Poco/Net/NetException.h>
#include<Poco/URI.h>
#include<Poco/DateTimeParser.h>
#include<Poco/Timestamp.h>
#include<Poco/Hash.h>
#include<string>
#include <ios>
#include <ctime>
#include <ostream>
#include <istream>
#include <sstream>
#include<unistd.h>
#include "unicode/platform.h"
#include "unicode/std_string.h"
#include "unicode/utypes.h"   /* Basic ICU data types */
//#include "unicode/ucnv.h"     /* C   Converter API    */
#include "unicode/ustring.h"  /* some more string fcns*/
#include "unicode/uchar.h"    /* char names           */
#include "unicode/uloc.h"
#include "unicode/unistr.h"
#include "pugixml.hpp"


CL_RSS_Quelle::QError::QError():ErrorDescription("") {};
CL_RSS_Quelle::QError::QError(const std::string& c):ErrorDescription(c) {};



CL_RSS_Quelle::CL_RSS_Quelle(const std::string& u):last_connection(0),last_feed_hash(0), last_news_hash(new std::set<std::size_t>),
     url(u), is_erroneous(false), last_error("")
{

};
CL_RSS_Quelle::CL_RSS_Quelle():last_connection(0),last_feed_hash(0), last_news_hash(new std::set<std::size_t>),
     url(""), is_erroneous(false), last_error("")
{

};

CL_RSS_Quelle::operator bool() const
{
     return url==""?false:true;
};

void CL_RSS_Quelle::set_url(const std::string& s)
{
     if(s!=url) {
          url=s;
          delete last_news_hash;
          last_news_hash = new std::set<std::size_t>;
          is_erroneous = false;
          last_error.set_error("");
          last_feed_hash=0;
     };
};
CL_RSS_Quelle& CL_RSS_Quelle::operator=(const CL_RSS_Quelle& q)
{
     if(this!=&q) {
          last_connection=q.last_connection;
          last_feed_hash=q.last_feed_hash;
          last_news_hash=new std::set<std::size_t>;
          *last_news_hash=*(q.last_news_hash);
          url=q.url;
          is_erroneous=q.is_erroneous;
          last_error=q.last_error;
     }
}

CL_RSS_Quelle::~CL_RSS_Quelle()
{
     if(!last_news_hash) delete last_news_hash;
}

std::string& CL_RSS_Quelle::get_response_utf8(std::string& body)
{
     body.clear();
     if(is_erroneous) throw last_error;
     Poco::URI uri(url);
     Poco::Net::HTTPClientSession client(uri.getHost(), uri.getPort());
     Poco::Net::HTTPRequest request(Poco::Net::HTTPRequest::HTTP_GET, uri.getPath());
     request.add("User-Agent","MK2366 Reader");
#warning TODO (markus#1#): application/rdf+xml, and application/atom+xml sollten berücksichtigt werden
     request.add("Accept","application/rss+xml, text/xml");
     try {
          client.sendRequest(request);
     } catch(Poco::Net::HostNotFoundException e) {
#warning TODO (markus#1#): Logging fehlt
          is_erroneous=true;
          last_error.set_error("Host not found");
          throw last_error;
     } catch(Poco::Net::DNSException e) {
#warning TODO (markus#1#): Logging fehlt
          is_erroneous=true;
          last_error.set_error("DNS Error");
          throw last_error;
     }
     Poco::Net::HTTPResponse response;
     std::istream& responsestream = client.receiveResponse(response);
     int status=response.getStatus();
     if (status!=200) {
          is_erroneous=true;
          last_error.set_error("HTTP Status code not 200");
          throw last_error;
     }
     const std::string& content_type = response.get("Content-Type");
     std::string scharset = "ISO 8859-1";
     size_t icharset=content_type.find("charset=");
     if (icharset!=content_type.npos) {
          icharset+=8;
          scharset=content_type.substr(icharset);
          int ionlyattrib = scharset.find(";");
          scharset=scharset.substr(0,ionlyattrib);
     };
     char c;
     while (responsestream.get(c)) {
          body+=c;
     }
     UnicodeString US(body.c_str(),scharset.c_str());
     body.clear();
     US.toUTF8String(body);
     return body;
}

int CL_RSS_Quelle::create_news_from_xml(std::string& xml_utf8, newsmap& map)
{
     /*jetzt wird es gefährlich; dieser Pointer muß am Ende dem Member last_news_hash zugewiesen werden
     und der free store des alten pointers freigegeben*/
     int zaehler=0;
     std::set<std::size_t>* news_hashes = new std::set<std::size_t>;
     pugi::xml_document rss_doc;
     std::istringstream xml_utf8_stream(xml_utf8);
     pugi::xml_parse_result result = rss_doc.load(xml_utf8_stream,pugi::parse_default,pugi::encoding_utf8);
#warning TODO (markus#1#): Auf mögliche Ergebnisse des Parsings reagieren
     pugi::xml_node version=rss_doc.child("rss");
     if(!version) {
          delete news_hashes;
          last_error.set_error("XML Paring: couldn't find rss version");
          throw last_error;
     }
     pugi::xml_attribute version_att=version.attribute("version");
     if(!version_att) {
          delete news_hashes;
          last_error.set_error("XML Paring: couldn't find rss version");
          throw last_error;
     }
     std::string sversion(version_att.value());
#warning TODO (markus#1#): Ich unterstütze im Augenblick nur rss 2.0; über andere Versionen habe ich mir keine Gedanken gemacht

     for(pugi::xml_node channel=version.child("channel"); channel; channel=channel.next_sibling("channel")) {
          CL_channel struct_channel;
          struct_channel=CL_channel();
          /*
                  struct_channel.image.url;
                  struct_channel.image.titel;
                  struct_channel.image.link;*/
          if(pugi::xml_node tmp_node=channel.child("title"))struct_channel.titel=tmp_node.child_value();
          if(pugi::xml_node tmp_node=channel.child("link"))struct_channel.url=tmp_node.child_value();
          if(pugi::xml_node tmp_node=channel.child("description"))struct_channel.description=tmp_node.child_value();
          if(pugi::xml_node tmp_node=channel.child("language"))struct_channel.language=tmp_node.child_value();
          if(pugi::xml_node tmp_node=channel.child("copyright"))struct_channel.copyright=tmp_node.child_value();
          if(pugi::xml_node tmp_node=channel.child("pubDate")) {
               std::string s=tmp_node.child_value();
               Poco::DateTime dateTime;
               int tzd;
               Poco::DateTimeParser::parse(s,dateTime, tzd);
               dateTime.makeUTC(tzd);
               Poco::Timestamp ts=dateTime.timestamp();
               struct_channel.pubDate=ts.epochTime();
          }
          if(pugi::xml_node tmp_node=channel.child("image")) {
               if(pugi::xml_node tmp_node2=tmp_node.child("url"))struct_channel.img.url=tmp_node2.child_value();
               if(pugi::xml_node tmp_node2=tmp_node.child("title"))struct_channel.img.titel=tmp_node2.child_value();
               if(pugi::xml_node tmp_node2=tmp_node.child("link"))struct_channel.img.link=tmp_node2.child_value();
          }

          for(pugi::xml_node item=channel.child("item"); item; item=item.next_sibling("item")) {
// TODO (markus#1#): Die RSS 2.0 Spec verlangt nicht, daß eine guid mitgegeben wird => verfeinern
               if(pugi::xml_node tmp_node=item.child("guid")) {
                    Poco::Hash<std::string> myhash;
                    std::size_t guid=myhash(tmp_node.child_value());
                    news_hashes->insert(guid);
                    if(last_news_hash->find(guid)==last_news_hash->end()) {
                         zaehler++;
                         CL_newsitem news;
                         if(pugi::xml_node tmp_node=item.child("author"))news.author=tmp_node.child_value();
                         if(pugi::xml_node tmp_node=item.child("description"))news.description=tmp_node.child_value();
                         if(pugi::xml_node tmp_node=item.child("link"))news.link=tmp_node.child_value();
                         if(pugi::xml_node tmp_node=item.child("title"))news.title=tmp_node.child_value();
                         if(pugi::xml_node tmp_node=item.child("pubDate")) {
                              std::string s=tmp_node.child_value();
                              Poco::DateTime dateTime;
                              int tzd;
                              Poco::DateTimeParser::parse(s,dateTime, tzd);
                              dateTime.makeUTC(tzd);
                              Poco::Timestamp ts=dateTime.timestamp();
                              map[guid].pubDate=ts.epochTime();
                         }
                         map[guid].guid=guid;
                         map[guid].newsitem=news;
                         map[guid].channel=struct_channel;
                    }
               }
          }
     }
     delete last_news_hash;
     last_news_hash=news_hashes;
     return zaehler;
}

std::map<std::size_t,CL_News> operator+(newsmap& m, CL_RSS_Quelle& q)
{
     if (q.is_erroneous||(time(NULL)-q.last_connection)<120 )return m;
     q.last_connection=time(NULL);
     std::string s="";
     Poco::Hash<std::string> myhash;
     size_t hash=myhash(q.get_response_utf8(s));
     if(hash==q.last_feed_hash) return m;
     q.last_feed_hash=hash;
     q.create_news_from_xml(s, m);
     return m;
}

std::map<std::size_t,CL_News> operator+=(newsmap& m, CL_RSS_Quelle& q)
{
     return m=m+q;
}





